package org.ricks.log;


import java.text.ChoiceFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.Format;
import java.util.Arrays;
import java.util.Iterator;

/**
 * Advances message formatter that replaces '{}' placeholders with given arguments.
 *
 * <p>
 * placeholders, and curly brackets can be escaped by a backslash.
 * </p>
 */
public class AdvancedMessageFormatter {

	private final DecimalFormatSymbols symbols;
	private final boolean escape;

	/**
	 *            Locale for formatting numbers
	 * @param escape
	 *            {@code true} to enable escaping by ticks, {@code false} to disable
	 */
	public AdvancedMessageFormatter(final boolean escape) {
		this.symbols = new DecimalFormatSymbols();
		this.escape = escape;
	}

	public String format(final String message, final Object[] arguments) {
		return format(message, Arrays.asList(arguments).iterator());
	}

	/**
	 * Formats a text message. All placeholders will be replaced with the given arguments.
	 *
	 * @param message
	 *            Text message with placeholders
	 * @param arguments
	 *            Replacements for placeholders
	 * @return Formatted text message
	 */
	private String format(final String message, final Iterator<Object> arguments) {
		int length = message.length();

		StringBuilder builder = new StringBuilder(length + 32);
		StringBuilder buffer = new StringBuilder(length + 32);
		StringBuilder current = builder;

		int openingTickIndex = -1;
		int openingCurlyBrackets = 0;

		for (int index = 0; index < length; ++index) {
			char character = message.charAt(index);
			if (escape && character == '\'' && index + 1 < length && openingCurlyBrackets == 0) {
				if (message.charAt(index + 1) == '\'') {
					current.append('\'');
					index += 1;
				} else {
					openingTickIndex = openingTickIndex < 0 ? index : -1;
				}
			} else if (character == '{' && index + 1 < length && arguments.hasNext() && openingTickIndex < 0) {
				if (openingCurlyBrackets++ == 0) {
					current = buffer;
				} else {
					current.append(character);
				}
			} else if (character == '}' && openingCurlyBrackets > 0 && openingTickIndex < 0) {
				if (--openingCurlyBrackets == 0) {
					if (buffer.length() == 0) {
						builder.append(arguments.next());
					} else {
						builder.append(format(buffer.toString(), arguments.next()));
						buffer.setLength(0);
					}
					buffer.setLength(0);
					current = builder;
				} else {
					current.append(character);
				}
			} else {
				current.append(character);
			}
		}

		if (buffer.length() > 0) {
			builder.append('{');
			builder.append(buffer);
		}

		if (openingTickIndex >= 0) {
			builder.insert(openingTickIndex, '\'');
		}

		return builder.toString();
	}

	/**
	 * Formats a pattern of a placeholder.
	 *
	 * @param pattern
	 *            Pattern of placeholder
	 * @param argument
	 *            Replacement for placeholder
	 * @return Formatted pattern
	 */
	private String format(final String pattern, final Object argument) {
		try {
			return getFormatter(pattern, argument).format(argument);
		} catch (IllegalArgumentException ex) {
			InternalLogger.log(Level.WARN, "Illegal argument '" + argument + "' for pattern '" + pattern + "'");
			return String.valueOf(argument);
		}
	}

	/**
	 * Gets the format object for a pattern of a placeholder. {@link ChoiceFormat} and {@link DecimalFormat} are
	 * supported.
	 *
	 * @param pattern
	 *            Pattern of placeholder
	 * @param argument
	 *            Replacement for placeholder
	 * @return Format object
	 */
	private Format getFormatter(final String pattern, final Object argument) {
		if (pattern.indexOf('|') != -1) {
			int start = pattern.indexOf('{');
			if (start >= 0 && start < pattern.lastIndexOf('}')) {
				return new ChoiceFormat(format(pattern, new EndlessIterator<Object>(argument)));
			} else {
				return new ChoiceFormat(pattern);
			}
		} else {
			return new DecimalFormat(pattern, symbols);
		}
	}

}
